/***************************************************************************
 *   Copyright (C) 2008 by Eduardo Gurgel Pinho                            *
 *   edgurgel@gmail.com                                                    *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 3 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
***************************************************************************/

#include "photontracer.h"

#include "photon.h"
#include "object/object.h"
#include "common/color.h"
#include "common/scene.h"
#include "math/vector.h"
#include "math/ray.h"
#include "light/light.h"
#include "material/light_material.h"
#include "material/object_material.h"
#include "material/color_material.h"
#include "material/texture_material.h"
#include "material/material.h"

namespace Sirat {

namespace Photon_Mapping {

Photontracer::Photontracer(Common::Scene* _scene,const unsigned short _depth,const unsigned int _photons) : scene(_scene) , depth(_depth) , photons(_photons) , random(-1,1) { 

}

Photontracer::~Photontracer(void) {
	if(scene) delete scene;
}

Math::Ray Photontracer::reflected_ray(const Math::Ray& _ray,const Math::Vector& _point,const Math::Vector& _normal) { 
	/* Prevent auto-reflection with a offset 0.001*/
	const Math::Vector point = _point+(_normal*0.001);
	
	const Math::Vector incident = _ray.get_direction().scalar_product(-1.0);
	
	const Math::Vector reflection = (_normal.scalar_product(_normal.dot_product(incident)*2.0)).sub_vect(incident);
	
	return Math::Ray(_point,reflection);
}

void Photontracer::trace_rec(Object::Object* _object, const Math::Ray& _ray, const Math::Vector& _point,const Common::Color& _photon,const unsigned short _depth) {
	if(!_depth) return;
	
	Math::Ray ray_ = reflected_ray(_ray, _point, _object->get_normal(_point));
	
	tuple<Object::Object*,Math::Vector> intersection = scene->nearest_object(ray_);
	Object::Object* object_ = intersection.get<0>();
	/**
	 * 0 means no intersection.
	 */
	if (object_ != 0) {
		/**
		 * Create new photon.
		 */
		
		Math::Vector point_ = intersection.get<1>();
		Common::Color reflectiveness = static_cast<Material::Color_Material*>(object_->get_material())->get_diffuse();
		
		Photon* photon_ = calculate_photon(object_,ray_,point_);
				
		photon_->set_color(_photon*reflectiveness);
		
		photon_map.insert(photon_);
		
		trace_rec(object_,ray_,point_,photon_->get_color(), _depth - 1); 
	}
}

Photon* Photontracer::calculate_photon(Object::Object* _object,const Math::Ray& _ray,const Math::Vector& _point) {
	Common::Color object_color; /*= _object->get_material()->get_diffuse();*/
						
	Math::Vector photon_direction = -_ray.get_direction();
						
	return new Photon(object_color,_point,photon_direction);
	
}


void Photontracer::trace(void) {
	
	photon_map.destroy_kd_tree();
	photon_map.clear();
		
	unsigned int n = 0;
	/**
	 * For each light. Trace new photons
	 */ 
	for(unsigned int l=0;l < scene->get_lights_size();l++) {
		Light::Light* light = scene->get_light(l);
		tuple<Math::Vector*,unsigned int> points = light->get_sample_points();
		/**
		 * Workaround
		 */
		Math::Ray ray(points.get<0>()[0],Math::Vector());
		
		for(unsigned int i = 0;i < photons ; i++) {
							
			Math::Vector direction(random.generate(),random.generate(),random.generate());
			ray.set_direction(direction.normalize());
			
			tuple<Object::Object*,Math::Vector> intersection = scene->nearest_object(ray);
			Object::Object* object = intersection.get<0>();
			
			/**
			 * 0 means no intersection.
			 */
			
			if(object != 0) {
				
				Math::Vector point = intersection.get<1>();								
				Common::Color light_color = static_cast<Material::Light_Material*>(light->get_material())->get_color()*static_cast<Material::Light_Material*>(light->get_material())->get_energy();
				Common::Color object_color = static_cast<Material::Color_Material*>(object->get_material())->get_diffuse();				
				Common::Color photon_color = light_color*object_color*(1.0/photons);				
				Math::Vector photon_direction = -ray.get_direction();
				
				n++;
				
				/**
				 * Indirect illumination only!
				 */
				//				Photon* photon = new Photon(photon_color,point,photon_direction);
				//photon_map.insert(photon);
				
				trace_rec(object,ray,point, photon_color,depth);
			}
		}
	}
	
// 	std::cout << "Total Photons: " << photon_map.get_size() << std::endl;
	
	photon_map.construct_kd_tree_rec();
	
// 	std::cout << "Photons: " << n << std::endl;
}

std::vector<tuple<Photon*,double> > Photontracer::reverse_trace(Object::Object* _object,const Math::Vector & _point, const double _radius, const unsigned int _n, const unsigned int _current_size) {
		
	std::vector<tuple<Photon*,double> > result;
	
	Math::Ray ray(_point);
	unsigned int n = 0;
	for(unsigned int i = 0; i < _n ; i++) {
							
		Math::Vector direction(random.generate(),random.generate(),random.generate());	
		ray.set_direction(direction.normalize());
			
		tuple<Object::Object*,Math::Vector> intersection = scene->nearest_object(ray);
		Object::Object* object = intersection.get<0>();
		
		if(object != 0) {
				
			Math::Vector point = intersection.get<1>();
					
			for(unsigned int l=0;l < scene->get_lights_size();l++) {
				Light::Light* light = scene->get_light(l);
				tuple<Math::Vector*,unsigned int> points = light->get_sample_points();
				/**
				 * Workaround
				 */
				Math::Vector L = points.get<0>()[0] - point;
				
				double distance = L.length();
				Math::Ray test(_point,L.normalize());
				if(!scene->object_occlusion(test,distance)) {
					Common::Color photon_color = static_cast<Material::Color_Material*>(_object->get_material())->get_diffuse() * static_cast<Material::Light_Material*>(light->get_material())->get_color() * static_cast<Material::Color_Material*>(object->get_material())->get_diffuse();
					Math::Vector photon_position = _point;
					Math::Vector photon_direction = direction;
					
 					result.push_back(make_tuple(new Photon(photon_color,photon_position,photon_direction),0.0));
					n++;
					break;
				}
			}
		}
	}
	return result;
}

Photon_Map * Photontracer::get_photon_map(void) {
	return &photon_map;
}

}

}


